Mdland SMART on FHIR OAuth 2.0 Guide

Using OAuth 2.0

Applications must secure and protect the privacy of patients and their data. To help meet this objective, MDLand supports using the OAuth 2.0 framework to authenticate and authorize applications.

Why OAuth 2.0?

OAuth 2.0 enables you to develop your application without having to build a credential management system. Instead of exposing login credentials to your application, your application and an EHR's authorization server exchange a series of authorization codes and access tokens. Your application can access protected patient data stored in an EHR's database after it obtains authorization from the EHR's authorization server.

Before You Get Started

To use OAuth 2.0 to authorize your application's access to patient information, some information needs to be shared between the authorization server and your application:

  1. client_id: The client_id identifies your application to authentication servers within the MDLand community and allows you to connect to any organization.
  2. redirect_uri: The redirect_uri confirms your identity and is used to validate and redirect authentication requests that originate from your application. MDLand's implementation allows for multiple redirect_uris.
  3. Credentials: Some apps, sometimes referred to as confidential clients, can use credentials registered for a given EHR system to obtain authorization to access the system without a user or a patient implicitly or explicitly authorizing the app. Examples of this are apps that use refresh tokens to allow users to launch the app outside of an MDLand client without needing to log in every time they use the app, and backend services that need to access resources without a specific person launching the app, for example, fetching data on a scheduled basis.

You'll provide information to us, including one or more redirect_uris, and MDLand will generate a client_id for you.

Apps can be launched from within an existing EHR or patient portal session, which is called an EHR launch. Alternatively, apps can be launched standalone from outside of an existing EHR session. Apps can also be backend services where no user is launching the app.

EHR launch (SMART on FHIR): The app is launched by the EHR calling a launch URL specified in the EHR's configuration. The EHR launches the launch URL and appends a launch token and the FHIR server's endpoint URL (ISS parameter) in the query string. The app exchanges the launch token, along with the client identification parameters to get an authorization code and eventually the access token.

Standalone launch: The app launches directly to the authorize endpoint outside of an EHR session and requests context from the EHR's authorization server.

Backend services: The app is not authorized by a specific person and likely does not have a user interface, and therefore calls EHR web services with system-level authorization.

EHR Launch (SMART on FHIR)

Contents

How It Works

Step 1: Your Application is Launched from the Patient Portal or EHR

Your app is launched by the EHR calling the launch URL which is specified in the EHR's configuration. The EHR sends a launch token and the FHIR server's endpoint URL (ISS parameter).

Step 2: Your Application Retrieves the Conformance Statement or SMART Configuration

To determine which authorize and token endpoints to use in the EHR launch flow, you should make a GET request to the metadata endpoint which is constructed by taking the iss provided and appending /metadata.

Metadata Example

Here's an example of what a full metadata request might look like.

GET https://api-fhir-proxy-2.mdland.net/metadata HTTP/1.1

Accept: application/fhir+json

 

Here's an example of what a smart-configuration request might look like.

GET https://api-fhir-proxy-2.mdland.net/.well-known/smart-configuration HTTP/1.1

Accept: application/json

Here is an example of what the authorize and token endpoints would look like in the smart-configuration response.

"authorization_endpoint": " https://api-fhir-proxy-2.mdland.net/authorize",

"token_endpoint": " https://api-fhir-proxy-2.mdland.net/token",

"token_endpoint_auth_methods_supported": [

    "client_secret_post",

    "client_secret_basic",

    "private_key_jwt"

]

Step 3: Your Application Requests an Authorization Code

Your application now has a launch token obtained from the initial EHR launch as well as the authorize endpoint obtained from the metadata query. To exchange the launch token for an authorization code, your app needs to either make an HTTP GET or POST request to the authorize endpoint that contains the parameters below. POST requests are supported for EHR launches in MDLand version November 2020 and later, and are not currently supported for standalone launches.

For POST requests, the parameters should be in the body of the request and should be sent as Content-Type "application/x-www-form-urlencoded". For GET requests, the parameters should be appended in the querystring of the request. It is MDLand's recommendation to use HTTP POST for EHR launches to overcome any size limits on the request URL.

In either case, the application should redirect the User-Agent (the user's browser) to the authorization server by either submitting an HTML Form to the authorization server (for HTTP POST) or by redirecting to a specified URL (for HTTP GET). The request should contain the following parameters.

Step 4: EHR's Authorization Server Reviews the Request

The EHR's authorization server reviews the request from your application. If approved, the authorization server redirects the browser to the redirect URL supplied in the initial request and appends the following querystring parameter.

Step 5: Your Application Exchanges the Authorization Code for an Access Token

After receiving the authorization code, your application trades the code for a JSON object containing an access token and contextual information by sending an HTTP POST to the token endpoint using a Content-Type header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.

Access Token Request: If You Are Not Using a Client Secret

The following parameters are required in the POST body:

The authorization server responds to the HTTP POST request with a JSON object that includes an access token. The response contains the following fields:

At this point, authorization is complete and the web application can access the protected patient data it requests using FHIR APIs.

Access Token Request: If You Are Using a Client Secret

Refresh tokens are not typically needed for embedded (SMART on FHIR) launches because users are not required to log in to MDLand during the SMART on FHIR launch process, and the access token obtained from the launch process is typically valid for longer than the user needs to use the app.

Consult the Standalone Launch: Access Token Request for Refresh Token Apps for details on how to obtain an access token if your app uses refresh tokens.

OpenID Connect id_tokens

A decoded OpenID Connect id_token JWT will have these headers:

Header

Description

alg

The JWT authentication algorithm.

typ

This is always set to JWT.

kid

The base64 encoded SHA-256 hash of the public key.

A decoded OpenID Connect id_token JWT will have this payload:

Claim

Description

Remarks

iss

Issuer of the JWT. This is set to the token endpoint that should be used by the client.

 

sub

STU3+ FHIR ID for the resource representing the user launching the app.

 

fhirUser

Absolute reference to the FHIR resource representing the user launching the app. See the HL7 documentation for more details.

 

aud

Audiences that the ID token is intended for. This will be set to the client ID for the application that was just authorized during the SMART on FHIR launch.

 

iat

Time integer for when the JWT was created, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC).

exp

Expiration time integer for this authentication JWT, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC).

This is set to the current time plus 5 minutes.

Step 6: Your Application Uses FHIR APIs to Access Patient Data

With a valid access token, your application can now access protected patient data from the EHR database using FHIR APIs. Queries must contain an Authorization header that includes the access token presented as a Bearer token.

Here's an example of what a valid query looks like:

GET https://api-fhir-proxy-2.mdland.net/Patient/47 HTTP/1.1

Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzZnhzNXY3UF9XaXZtbmJ3Z1RDVVpCUXJ3VXdkaFE3V09ad19lT05DdTJRIn0.eyJleHAiOjE2NjkzMDIwNDgsImlhdCI6MTY2OTMwMTc0OCwiYXV0aF90aW1lIjoxNjY5MzAxNzQ4LCJqdGkiOiIwMDRmYTg1Ni1hM2EwLTQxMDctYWQ1Zi05MTkzNzdmZmE4MmEiLCJpc3MiOiJodHRwczovL2FwaS1hdXRoLTIubWRsYW5kLm5ldC9yZWFsbXMvRzEwIiwic3ViIjoiYTIwMzBlMDMtMzBkZi00MDkyLThkYjItMGFmOGQzM2QyYzM4IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdF9hdXRoX3Byb3h5Iiwic2Vzc2lvbl9zdGF0ZSI6IjA5ZWM3MThkLWQ3MWEtNGVlOS05MjM5LTMyZjI1ZjFmOGMyYyIsImFjciI6IjEiLCJzY29wZSI6InBhdGllbnQvTWVkaWNhdGlvblJlcXVlc3QucmVhZCBwYXRpZW50L0RvY3VtZW50UmVmZXJlbmNlLnJlYWQgcGF0aWVudC9Pcmdhbml6YXRpb24ucmVhZCBwYXRpZW50L0NhcmVUZWFtLnJlYWQgcGF0aWVudC9JbW11bml6YXRpb24ucmVhZCBwYXRpZW50L0NvbmRpdGlvbi5yZWFkIGVtYWlsIHBhdGllbnQvR29hbC5yZWFkIHBhdGllbnQvRW5jb3VudGVyLnJlYWQgcGF0aWVudC9PYnNlcnZhdGlvbi5yZWFkIHBhdGllbnQvTG9jYXRpb24ucmVhZCBsYXVuY2gvcGF0aWVudCBwYXRpZW50L0FsbGVyZ3lJbnRvbGVyYW5jZS5yZWFkIHBhdGllbnQvUHJvdmVuYW5jZS5yZWFkIHBhdGllbnQvUGF0aWVudC5yZWFkIHBhdGllbnQvRGlhZ25vc3RpY1JlcG9ydC5yZWFkIHBhdGllbnQvUHJvY2VkdXJlLnJlYWQgcGF0aWVudC9EZXZpY2UucmVhZCBwYXRpZW50L01lZGljYXRpb24ucmVhZCBvcGVuaWQgcGF0aWVudC9QcmFjdGl0aW9uZXJSb2xlLnJlYWQgcGF0aWVudC9DYXJlUGxhbi5yZWFkIGZoaXJVc2VyIG9mZmxpbmVfYWNjZXNzIHBhdGllbnQvUHJhY3RpdGlvbmVyLnJlYWQiLCJzaWQiOiIwOWVjNzE4ZC1kNzFhLTRlZTktOTIzOS0zMmYyNWYxZjhjMmMiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInBhdGllbnQiOiI0NyIsImZoaXJVc2VyIjoiaHR0cHM6Ly9oYXBpLWZoaXIubWRsYW5kLm5ldC9maGlyL1BhdGllbnQvNDcifQ.C-Yo3IxTNI5HCqfcX7xUwe11KBgA_unvIxBz3-y7NnnYPb6UewEh3WXSA1CEAL6m9s4DcQxc8f1zQ3NpJh62Gc6F7_bJ0ERe0lYqxK7FL_ANVPK1ge8adz6W3r_RWaOxkOuUTdfEfuDN9_IORsWmHZrXbIB8hU_CZBbY5eK5ssLRym7SYglSmMO_jYfms6Alm2V1GCNULmwLcPmYEle5Wx9JpBMtdsLUkghRLxRA4eZ4YLD6UdmvrCY_zToN8ArHXXucjqyoeJSTK6DcDsSrRPQsXCQBAMLX4FDnNJ_3iHet0Fmhuvq0EXZPuRzgiCo0VFuSkwZGasRqFnE_JTrGtg

Step 7: Use a Refresh Token to Obtain a New Access Token

Refresh tokens are not typically needed for embedded (SMART on FHIR) launches because users are not required to log in to MDLand during the SMART on FHIR launch process, and the access token obtained from the launch process is typically valid for longer than the user needs to use the app.

Consult the Standalone Launch: Use a Refresh Token to Obtain a New Access Token for details on how to use a refresh token to get a new an access token if your app uses refresh tokens.

Standalone Launch

Contents

How It Works

Step 1: Your Application Requests an Authorization Code

Your application would like to authenticate the user using the OAuth 2.0 workflow. To initiate this process, your app needs to link (using HTTP GET) to the authorize endpoint and append the following querystring parameters:

Step 2: EHR's Authorization Server Authenticates the User and Authorizes Access

The EHR's authorization server reviews the request from your application, authenticates the user (sample credentials found here), and authorizes access If approved, the authorization server redirects the browser to the redirect URL supplied in the initial request and appends the following querystring parameter.

Step 3: Your Application Exchanges the Authorization Code for an Access Token

After receiving the authorization code, your application trades the code for a JSON object containing an access token and contextual information by sending an HTTP POST to the token endpoint using a Content-Type header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.

Access Token Request: If You Are Not Using a Client Secret

The following parameters are required in the POST body:

The authorization server responds to the HTTP POST request with a JSON object that includes an access token. The response contains the following fields:

At this point, authorization is complete and the web application can access the protected patient data it requested using FHIR APIs.

Access Token Request: If You Are Using a Client Secret:

After receiving the authorization code, your application trades the code for a JSON object containing an access token and contextual information by sending an HTTP POST to the token endpoint using a Content-Type header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.

The following parameters are required in the POST body:

Note: The client_id parameter is not passed in the the POST body if you use client secret authentication, which is different from the access token request for apps that do not use refresh tokens. You will instead pass an Authorization HTTP header with client_id and client_secret URL encoded and passed as a username and password. Conceptually the Authorization HTTP header will have this value: base64(client_id:client_secret).

For example, using the following client_id and client_secret:

client_id: d45049c3-3441-40ef-ab4d-b9cd86a17225

URL encoded client_id: d45049c3-3441-40ef-ab4d-b9cd86a17225 Note: base64 encoding MDLand's client IDs will have no effect

client_secret: this-is-the-secret-2/7

URL encoded client_secret: this-is-the-secret-2%2F7

Would result in this Authorization header:

Authorization: Basic base64Encode{d45049c3-3441-40ef-ab4d-b9cd86a17225:this-is-the-secret-2%2F7}

or

Authorization: Basic ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==

Here's an example of what a valid HTTP POST might look like:

POST https://api-fhir-proxy-2.mdland.net/token HTTP/1.1

Content-Type: application/x-www-form-urlencoded

Authorization: Basic ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==

 

grant_type=authorization_code&code=yfNg-rSc1t5O2p6jVAZLyY00uOOte5KM1y3YUxqsJQnBKEMNsYqOPTyVqcCH3YXaPkLztO9Rvf7bhLqQTwALHcHN6raxpTbR1eVgV2QyLA_4K0HrJO92et3qRXiXPkj7&redirect_uri=https%3A%2F%2Ffhir.epic.com%2Ftest%2Fsmart

The authorization server responds to the HTTP POST request with a JSON object that includes an access token and a refresh token. The response contains the following fields:

Note that you can pass additional parameters if needed based on the integration configuration. Here's an example of what a JSON object including an access token and refres token might look like:

{

  "access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzZnhzNXY3UF9XaXZtbmJ3Z1RDVVpCUXJ3VXdkaFE3V09ad19lT05DdTJRIn0.eyJleHAiOjE2NjkzMDIwNDgsImlhdCI6MTY2OTMwMTc0OCwiYXV0aF90aW1lIjoxNjY5MzAxNzQ4LCJqdGkiOiIwMDRmYTg1Ni1hM2EwLTQxMDctYWQ1Zi05MTkzNzdmZmE4MmEiLCJpc3MiOiJodHRwczovL2FwaS1hdXRoLTIubWRsYW5kLm5ldC9yZWFsbXMvRzEwIiwic3ViIjoiYTIwMzBlMDMtMzBkZi00MDkyLThkYjItMGFmOGQzM2QyYzM4IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdF9hdXRoX3Byb3h5Iiwic2Vzc2lvbl9zdGF0ZSI6IjA5ZWM3MThkLWQ3MWEtNGVlOS05MjM5LTMyZjI1ZjFmOGMyYyIsImFjciI6IjEiLCJzY29wZSI6InBhdGllbnQvTWVkaWNhdGlvblJlcXVlc3QucmVhZCBwYXRpZW50L0RvY3VtZW50UmVmZXJlbmNlLnJlYWQgcGF0aWVudC9Pcmdhbml6YXRpb24ucmVhZCBwYXRpZW50L0NhcmVUZWFtLnJlYWQgcGF0aWVudC9JbW11bml6YXRpb24ucmVhZCBwYXRpZW50L0NvbmRpdGlvbi5yZWFkIGVtYWlsIHBhdGllbnQvR29hbC5yZWFkIHBhdGllbnQvRW5jb3VudGVyLnJlYWQgcGF0aWVudC9PYnNlcnZhdGlvbi5yZWFkIHBhdGllbnQvTG9jYXRpb24ucmVhZCBsYXVuY2gvcGF0aWVudCBwYXRpZW50L0FsbGVyZ3lJbnRvbGVyYW5jZS5yZWFkIHBhdGllbnQvUHJvdmVuYW5jZS5yZWFkIHBhdGllbnQvUGF0aWVudC5yZWFkIHBhdGllbnQvRGlhZ25vc3RpY1JlcG9ydC5yZWFkIHBhdGllbnQvUHJvY2VkdXJlLnJlYWQgcGF0aWVudC9EZXZpY2UucmVhZCBwYXRpZW50L01lZGljYXRpb24ucmVhZCBvcGVuaWQgcGF0aWVudC9QcmFjdGl0aW9uZXJSb2xlLnJlYWQgcGF0aWVudC9DYXJlUGxhbi5yZWFkIGZoaXJVc2VyIG9mZmxpbmVfYWNjZXNzIHBhdGllbnQvUHJhY3RpdGlvbmVyLnJlYWQiLCJzaWQiOiIwOWVjNzE4ZC1kNzFhLTRlZTktOTIzOS0zMmYyNWYxZjhjMmMiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsInBhdGllbnQiOiI0NyIsImZoaXJVc2VyIjoiaHR0cHM6Ly9oYXBpLWZoaXIubWRsYW5kLm5ldC9maGlyL1BhdGllbnQvNDcifQ.C-Yo3IxTNI5HCqfcX7xUwe11KBgA_unvIxBz3-y7NnnYPb6UewEh3WXSA1CEAL6m9s4DcQxc8f1zQ3NpJh62Gc6F7_bJ0ERe0lYqxK7FL_ANVPK1ge8adz6W3r_RWaOxkOuUTdfEfuDN9_IORsWmHZrXbIB8hU_CZBbY5eK5ssLRym7SYglSmMO_jYfms6Alm2V1GCNULmwLcPmYEle5Wx9JpBMtdsLUkghRLxRA4eZ4YLD6UdmvrCY_zToN8ArHXXucjqyoeJSTK6DcDsSrRPQsXCQBAMLX4FDnNJ_3iHet0Fmhuvq0EXZPuRzgiCo0VFuSkwZGasRqFnE_JTrGtg",

  "expires_in": 300,

  "refresh_expires_in": 0,

  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI1ZjJiNmU1Zi02MGI3LTQ3M2QtOThlYS05OTMzMWZlNWM2ZWYifQ.eyJpYXQiOjE2NjkzMDE3NDgsImp0aSI6ImIzYmZmMDk2LTFjZWUtNDc3Yi04MjhiLTM0YjZiZjMzNTZhNyIsImlzcyI6Imh0dHBzOi8vYXBpLWF1dGgtMi5tZGxhbmQubmV0L3JlYWxtcy9HMTAiLCJhdWQiOiJodHRwczovL2FwaS1hdXRoLTIubWRsYW5kLm5ldC9yZWFsbXMvRzEwIiwic3ViIjoiYTIwMzBlMDMtMzBkZi00MDkyLThkYjItMGFmOGQzM2QyYzM4IiwidHlwIjoiT2ZmbGluZSIsImF6cCI6InRlc3RfYXV0aF9wcm94eSIsInNlc3Npb25fc3RhdGUiOiIwOWVjNzE4ZC1kNzFhLTRlZTktOTIzOS0zMmYyNWYxZjhjMmMiLCJzY29wZSI6InBhdGllbnQvTWVkaWNhdGlvblJlcXVlc3QucmVhZCBwYXRpZW50L0RvY3VtZW50UmVmZXJlbmNlLnJlYWQgcGF0aWVudC9Pcmdhbml6YXRpb24ucmVhZCBwYXRpZW50L0NhcmVUZWFtLnJlYWQgcGF0aWVudC9JbW11bml6YXRpb24ucmVhZCBwYXRpZW50L0NvbmRpdGlvbi5yZWFkIGVtYWlsIHBhdGllbnQvR29hbC5yZWFkIHBhdGllbnQvRW5jb3VudGVyLnJlYWQgcGF0aWVudC9PYnNlcnZhdGlvbi5yZWFkIHBhdGllbnQvTG9jYXRpb24ucmVhZCBsYXVuY2gvcGF0aWVudCBwYXRpZW50L0FsbGVyZ3lJbnRvbGVyYW5jZS5yZWFkIHBhdGllbnQvUHJvdmVuYW5jZS5yZWFkIHBhdGllbnQvUGF0aWVudC5yZWFkIHBhdGllbnQvRGlhZ25vc3RpY1JlcG9ydC5yZWFkIHBhdGllbnQvUHJvY2VkdXJlLnJlYWQgcGF0aWVudC9EZXZpY2UucmVhZCBwYXRpZW50L01lZGljYXRpb24ucmVhZCBvcGVuaWQgcGF0aWVudC9QcmFjdGl0aW9uZXJSb2xlLnJlYWQgcGF0aWVudC9DYXJlUGxhbi5yZWFkIGZoaXJVc2VyIG9mZmxpbmVfYWNjZXNzIHBhdGllbnQvUHJhY3RpdGlvbmVyLnJlYWQiLCJzaWQiOiIwOWVjNzE4ZC1kNzFhLTRlZTktOTIzOS0zMmYyNWYxZjhjMmMifQ.oS3dx92NgzV_IjnkvZm4tKhvcbtsZqniEZ0xrBcrgvc",

  "token_type": "Bearer",

  "id_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIzZnhzNXY3UF9XaXZtbmJ3Z1RDVVpCUXJ3VXdkaFE3V09ad19lT05DdTJRIn0.eyJleHAiOjE2NjkzMDIwNDgsImlhdCI6MTY2OTMwMTc0OCwiYXV0aF90aW1lIjoxNjY5MzAxNzQ4LCJqdGkiOiJkYmRjMzVkMy1mZGJlLTQzZmYtODM0Yi1jOTMyMWYyMTM2ZTgiLCJpc3MiOiJodHRwczovL2FwaS1hdXRoLTIubWRsYW5kLm5ldC9yZWFsbXMvRzEwIiwiYXVkIjoidGVzdF9hdXRoX3Byb3h5Iiwic3ViIjoiYTIwMzBlMDMtMzBkZi00MDkyLThkYjItMGFmOGQzM2QyYzM4IiwidHlwIjoiSUQiLCJhenAiOiJ0ZXN0X2F1dGhfcHJveHkiLCJzZXNzaW9uX3N0YXRlIjoiMDllYzcxOGQtZDcxYS00ZWU5LTkyMzktMzJmMjVmMWY4YzJjIiwiYXRfaGFzaCI6IlB6VVdXc2xEalRXdlFjY1VLa1g2TXciLCJhY3IiOiIxIiwic2lkIjoiMDllYzcxOGQtZDcxYS00ZWU5LTkyMzktMzJmMjVmMWY4YzJjIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwYXRpZW50IjoiNDciLCJmaGlyVXNlciI6Imh0dHBzOi8vaGFwaS1maGlyLm1kbGFuZC5uZXQvZmhpci9QYXRpZW50LzQ3In0.NpEaxnfgzcknOmP3J9nc9-1fp1slF0PzJSXz9015_pmdqlcrLLArWAN8-Q_2biM02QSHl7MEjbaaPUz63VyQIoQrsbtIvHwQWOnFHJkjyv_u6CiG4rwMHUHL7Sb7zW5y7c-6fiiUb4b73OK6ocGeLS9cvKatJPMc4awHo2_b6WoApnZu7aDZlwxrAqnfUS1XxT6Zb9_ZPaw6c7fEHP5Gu7-0avvW3GhdnELwJVj1aJVjYviIqMmo_lAVjhwuZwBygXbDRYK0WjiwdGBXiQQnKfuKrnSlhlTbzTURofC9wF__BvBN7CcKtc5cyWaN9QM0zeiad5ULrW2DJSd4jy5f7g",

  "not-before-policy": 1669044176,

  "session_state": "09ec718d-d71a-4ee9-9239-32f25f1f8c2c",

  "scope": "patient/MedicationRequest.read patient/DocumentReference.read patient/Organization.read patient/CareTeam.read patient/Immunization.read patient/Condition.read email patient/Goal.read patient/Encounter.read patient/Observation.read patient/Location.read launch/patient patient/AllergyIntolerance.read patient/Provenance.read patient/Patient.read patient/DiagnosticReport.read patient/Procedure.read patient/Device.read patient/Medication.read openid patient/PractitionerRole.read patient/CarePlan.read fhirUser offline_access patient/Practitioner.read",

  "smart_style_url": "https://api-fhir-proxy-2.mdland.net/smart_style",

  "need_patient_banner": false,

  "patient": "47"

}

At this point, authorization is complete and the web application can access the protected patient data it requested using FHIR APIs.

Step 4: Your Application Uses FHIR APIs to Access Patient Data

With a valid access token, your application can now access protected patient data from the EHR database using FHIR APIs. Queries must contain an Authorization header that includes the access token presented as a Bearer token.

Step 5: Use a Refresh Token to Obtain a New Access Token

If your app uses refresh tokens (i.e. it can securely store credentials), then you can use a refresh token to request a new access token when the current access token expires (determined by the expires_in field from the authorization response from step 3).

Your application trades the refresh_token for a JSON object containing a new access token and contextual information by sending an HTTP POST to the token endpoint using a Content-Type HTTP header with value of "application/x-www-form-urlencoded". For more information, see RFC 6749 section 4.1.3.

The following parameters are required in the POST body:

An Authorization header using HTTP Basic Authentication is required, where the username is the URL encoded client_id and the password is the URL encoded client_secret.

For example, using the following client_id and client_secret:

client_id: d45049c3-3441-40ef-ab4d-b9cd86a17225

URL encoded client_id: d45049c3-3441-40ef-ab4d-b9cd86a17225 Note: base64 encoding MDLand's client IDs will have no effect

client_secret: this-is-the-secret-2/7

URL encoded client_secret: this-is-the-secret-2%2F7

Would result in this Authorization header:

Authorization: Basic base64Encode{d45049c3-3441-40ef-ab4d-b9cd86a17225:this-is-the-secret-2%2F7}

or

Authorization: Basic ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==

Here's an example of what a valid HTTP POST might look like:

POST https://api-fhir-proxy-2.mdland.net/token HTTP/1.1

Authorization: Basic ZDQ1MDQ5YzMtMzQ0MS00MGVmLWFiNGQtYjljZDg2YTE3MjI1OnRoaXMtaXMtdGhlLXNlY3JldC0yJTJGNw==

Content-Type: application/x-www-form-urlencoded

 

grant_type=refresh_token&refresh_token=j12xcniournlsdf234bgsd

The authorization server responds to the HTTP POST request with a JSON object that includes the new access token. The response contains the following fields:

An example response to the previous request may look like the following:

{

"access_token": "57CjhZEdiTcCh1nqIwQUw5rODOLP3bSTnMEGNYbBerSeNn8hIUm6Mlc5ruCTfQawjRAoR8sYr8S_7vNdnJfgRKfD6s8mOqPnvJ8vZOHjEvy7l3Ra9frDaEAUBbN-j86k",

"token_type": "bearer",

"expires_in": 3240,

"scope": "Patient.read Patient.search ",

"refresh_token": "b72foiua9asdhnkjanvm"

}

SMART Backend Services (Backend OAuth 2.0)

Contents

Overview

Backend apps (i.e. apps without direct end user or patient interaction) can also use OAuth 2.0 authentication through the client_credentials OAuth 2.0 grant type. MDLand's OAuth 2.0 implementation for backend services follows the SMART Backend Services: Authorization Guide, though it currently differs from that profile in some respects. Application vendors pre-register a public key for a given MDLand community member on the MDLand on FHIR website and then use the corresponding private key to sign a JSON Web Token (JWT) which is presented to the authorization server to obtain an access token.

Building a Backend OAuth 2.0 App

To use the client_credentials OAuth 2.0 grant type to authorize your backend application's access to patient information, two pieces of information need to be shared between the authorization server and your application:

  1. Client ID: The client ID identifies your application to authentication servers within the MDLand community and allows you to connect to any organization.
  2. Public key: The public key is used to validate your signed JSON Web Token to confirm your identity.

Using a JWT to Obtain an Access Token for a Backend Service

You will generate a one-time use JSON Web Token (JWT) to authenticate your app to the authorization server and obtain an access token that can be used to authenticate your app's web service calls. There are several libraries for creating JWTs. See jwt.io for some examples.

Step 1: Creating the JWT

See the Creating a Public Private Key Pair for JWT Signature and the Creating a JWT sections for details on how to create a signed JWT.

Step 2: POSTing the JWT to Token Endpoint to Obtain Access Token

Your application makes a HTTP POST request to the authorization server's OAuth 2.0 token endpoint to obtain access token. The following form-urlencoded parameters are required in the POST body:

Here is an example request:

POST https://api-fhir-proxy-2.mdland.net/token HTTP/1.1

Content-Type: application/x-www-form-urlencoded

 

grant_type=client_credentials&client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer&client_assertion=eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJzdWIiOiJkNDUwNDljMy0zNDQxLTQwZWYtYWI0ZC1iOWNkODZhMTcyMjUiLCJhdWQiOiJodHRwczovL2ZoaXIuZXBpYy5jb20vaW50ZXJjb25uZWN0LWZoaXItb2F1dGgvb2F1dGgyL3Rva2VuIiwianRpIjoiZjllYWFmYmEtMmU0OS0xMWVhLTg4ODAtNWNlMGM1YWVlNjc5IiwiZXhwIjoxNTgzNTI0NDAyLCJuYmYiOjE1ODM1MjQxMDIsImlhdCI6MTU4MzUyNDEwMn0.dztrzHo9RRwNRaB32QxYLaa9CcIMoOePRCbpqsRKgyJmBOGb9acnEZARaCzRDGQrXccAQ9-syuxz5QRMHda0S3VbqM2KX0VRh9GfqG3WJBizp11Lzvc2qiUPr9i9CqjtqiwAbkME40tioIJMC6DKvxxjuS-St5pZbSHR-kjn3ex2iwUJgPbCfv8cJmt19dHctixFR6OG-YB6lFXXpNP8XnL7g85yLOYoQcwofN0k8qK8h4uh8axTPC21fv21mCt50gx59XgKsszysZnMDt8OG_G4gjk_8JnGHwOVkJhqj5oeg_GdmBhQ4UPuxt3YvCOTW9S2vMikNUnxrhdVvn2GVg

And here is an example response body assuming the authorization server approves the request:

{

"access_token": "i82fGhXNxmidCt0OdjYttm2x0cOKU1ZbN6Y_-zBvt2kw3xn-MY3gY4lOXPee6iKPw3JncYBT1Y-kdPpBYl-lsmUlA4x5dUVC1qbjEi1OHfe_Oa-VRUAeabnMLjYgKI7b",

"token_type": "bearer",

"expires_in": 3600,

"scope": "Patient.read Patient.search"

}

JSON Web Tokens (JWTs)

Contents

Background

JSON Web Tokens (JWTs) can be used for some OAuth 2.0 workflows. JWTs are required to be used by the client credentials flow used by backend services, and can be used, as an alternative to client secrets, for apps that use refresh tokens. You will generate a one-time use JWT to authenticate your app to the authorization server and obtain an access token that can be used to authenticate your app's web service calls, and potentially a refresh token that can be used to get another access token without a user needing to authorize the request. There are several libraries for creating JWTs. See jwt.io for some examples.

Creating a JWT

The JWT should have these headers:

Header

Description

alg

The JWT authentication algorithm.

typ

This should always be set to JWT.

kid

For apps using JSON Web Key Sets (including dynamically registed clients), set this value to the kid of the target public key from your key set

jku

For apps using JSON Web Key Set URLs, optionally set this value to the URL you registered on your application

The JWT header should be formatted as follows:

{

"alg": "RS384",

"typ": "JWT"

}

The JWT should have these claims in the payload:

Claim

Description

Remarks

iss

Issuer of the JWT. This is the app's client_id, as determined during registration on the MDLand on FHIR website, or the client_id returned during a dynamic registration

This is the same as the value for the sub claim.

sub

Issuer of the JWT. This is the app's client_id, as determined during registration on the MDLand on FHIR website, or the client_id returned during a dynamic registration

This is the same as the value for the iss claim.

aud

The FHIR authorization server's token endpoint URL. This is the same URL to which this authentication JWT will be posted. See below for an example POST.

It's possible that MDLand community member systems will route web service traffic through a proxy server, in which case the URL the JWT is posted to is not known to the authorization server, and the JWT will be rejected. For such cases, MDLand community member administrators can add additional audience URLs to the allowlist, in addition to the FHIR server token URL if needed.

jti

A unique identifier for the JWT.

The jti must be no longer than 151 characters and cannot be reused during the JWT's validity period, i.e. before the exp time is reached.

exp

Expiration time integer for this authentication JWT, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC).

The exp value must be in the future, and can be no more than 5 minutes in the future at the time the access token request is received.

nbf

Time integer before which the JWT must not be accepted for processing, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC).

The nbf value cannot be in the future, cannot be more recent than the exp value, and the exp - nbf difference cannot be greater than 5 minutes.

iat

Time integer for when the JWT was created, expressed in seconds since the "Epoch" (1970-01-01T00:00:00Z UTC).

The iat value cannot be in the future, and the exp - iat difference cannot be greater than 5 minutes.

Here's an example JWT payload:

{

"iss": "d45049c3-3441-40ef-ab4d-b9cd86a17225",

"sub": "d45049c3-3441-40ef-ab4d-b9cd86a17225",

"aud": "https://api-fhir-proxy-2.mdland.net/token",

"jti": "f9eaafba-2e49-11ea-8880-5ce0c5aee679",

"exp": 1583524402,

"nbf": 1583524102,

"iat": 1583524102

}

The header and payload are then base64 URL encoded, combined with a period separating them, and cryptographically signed using the private key to generate a signature. Conceptually:

signature = RSA-SHA384(base64urlEncoding(header) + '.' + base64urlEncoding(payload), privatekey)

The full JWT is constructed by combining the header, body and signature as follows:

base64urlEncoding(header) + '.' + base64urlEncoding(payload) + '.' + base64urlEncoding(signature)

JSON Web Key Sets

Applications using JSON Web Token (JWT) authentication can provide their public keys to MDLand as a JSON Web Key (JWK) Set. Each key in the set should be of type RSA and have the ktyn , e and kid fields present:

        {

            "keys": [

                { "kty":"RSA", "e": "AQAB", "kid": "d1fc1715-3299-43ec-b5de-f943803314c2", "n": "uPkpNCkqbbismKNwKguhL0P4Q40sbyUkUFcmDAACqBntWerfjv9VzC3cAQjwh3NpJyRKf7JvwxrbELvPRMRsXefuEpafHfNAwj3acTE8xDRSXcwzQwd7YIHmyXzwHDfmOSYW7baAJt-g_FiqCV0809M9ePkTwNvjpb6tlJu6AvrNHq8rVn1cwvZLIG6KLCTY-EHxNzsBblJYrZ5YgR9sfBDo7R-YjE8c761PSrBmUM4CAQHtQu_w2qa7QVaowFwcOkeqlSxZcqqj8evsmRfqJWoCgAAYeRIsgKClZaY5KC1sYHIlLs2cp2QXgi7rb5yLUVBwpSWM4AWJ_J5ziGZBSQYB4sWWn8bjc5-k1JpUnf88-UVZv9vrrkMJjNam32Z6FNm4g49gCVu_TH5M83_pkrsNWwCu1JquY9Z-eVNCsU_AWPgHeVZyXT6giHXZv_ogMWSh-3opMt9dzPwYseG9gTPXqDeKRNWFEm46X1zpcjh-sD-8WcAlgaEES6ys_O8Z" },

                { "kty":"RSA", "e":"AQAB","kid":"1248110c-afbd-484c-b75b-b30200ffcf05","n":"zYlmlLzhQNpRq263CoShHgyJCrNLm6oFrf0FTQ0pwYIyaTFZirW-G6wDyVwtaQA5Kjth0NjglQIYfPR9rPBGmX3CSVtmZCLEhFeMMp_W3SoZFZJJypP4x2AbG7TqPvkbpuYWHT15Lvp9amgGqdNQ-UyPZS8WMp1NloDw4R9Nomo8yEk8OcgmG1ORqrmFLam3AHPvZZH2wp88gE9DWOAoOwOpDP32QSo1ii0Wrw2gV_UwsVuAjhkFK3asOCHoqlRhm_c49d1OgB7QvrnP-S5-Wa1JsQ5lKa52G7UVkyNPA1pFw7iW7PHXCgoe8inSmnrqkzmtmDjpOnqpuGU39I6byQ"}

            ]

        }                  

   

Key Identifier Requirements

MDLand recommends your application provide the kid field in JWK Sets and in your JWT Headers, since MDLand can use the identifier to find your intended key. MDLand does not require your key identifiers to be thumbprints, and will accept any value that is unique within your key set.

Hosting a JWK Set URL

Apps using JWT authentication need use a JSON Web Key Set URL to provide public keys to MDLand for JWT verification. JWK Set URLs streamline implementation and make key rotation feasible by providing a centralized and trusted place where MDLand community members can retrieve your public keys.

Registering a JWK Set URL

When building or licensing an application that will authenticate against MDLand, you are prompted to optionally upload JWK Set URL to your app. When registering this URL, be sure of the following:

  1. Are secured with TLS
  2. Are publicly accessible
  3. Do not require authentication
  4. Will not change over time
  5. Are responsive